Skip to content

fix(server-elysia): update test mocks to match refactored http.createServer (#1204)#1210

Open
kagura-agent wants to merge 4 commits intoVoltAgent:mainfrom
kagura-agent:fix/elysia-server-provider-tests
Open

fix(server-elysia): update test mocks to match refactored http.createServer (#1204)#1210
kagura-agent wants to merge 4 commits intoVoltAgent:mainfrom
kagura-agent:fix/elysia-server-provider-tests

Conversation

@kagura-agent
Copy link
Copy Markdown

@kagura-agent kagura-agent commented Apr 17, 2026

Summary

Fixes #1204 — all 6 tests in elysia-server-provider.spec.ts were failing because the test mocks targeted the old app.listen() API, but startServer() was refactored to use http.createServer() + server.listen().

Changes

  • Added vi.mock('node:http') to mock createServer instead of relying on mockApp.listen
  • Updated beforeEach to configure mock server behavior (listen/close/once callbacks)
  • Added fetch to mock app (required by the HTTP request handler)
  • Updated assertions to verify createServer and server.listen calls
  • Updated stop test to verify server.close() is called
  • Updated error test to simulate errors via server.once('error') handler

Testing

All 6 tests pass:

✓ should start the server
✓ should stop the server
✓ should throw if already running
✓ should configure websocket if enabled
✓ should extract and display custom endpoints from configureApp
✓ should handle startup errors and release port

Test Files  1 passed (1)
Tests       6 passed (6)

Closes #1204


Summary by cubic

Updates Elysia server tests to mock node:http (createServer/server.listen) after the startServer refactor, restoring all six cases (fixes #1204). Secures the dev auth bypass in @voltagent/server-core by requiring NODE_ENV=development or test only (fail-closed), applies the same check to WebSocket paths, and cleans up lint errors.

  • Bug Fixes
    • @voltagent/server-elysia: mock node:http; assert createServer, server.listen, and server.close; simulate startup errors via server.once('error').
    • @voltagent/server-core: add isDevEnvironment(); update isDevRequest() and WS dev bypass to use it; treat empty/undefined NODE_ENV as production; add unit tests.
    • Resolve lint errors (formatting, unused params).

Written for commit bcb54c8. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Development auth bypass now only activates when the environment is explicitly set to development or test; undefined/empty environments are treated as production (fail-closed), preventing accidental bypass.
  • Tests

    • Expanded test coverage for environment detection, auth bypass rules (including fail-closed cases), WebSocket dev checks, and server start/stop behavior with improved mocks for startup/error handling.

…oltAgent#1206)

Previously isDevRequest() treated any non-production NODE_ENV (including
undefined) as a development environment, allowing auth bypass with a simple
header. Deployed servers that forgot NODE_ENV=production were fully open.

Now only NODE_ENV=development or NODE_ENV=test enable the dev bypass
(fail-closed). Undefined/empty NODE_ENV is treated as production.
…ment helper

Address CodeRabbit review: WebSocket auth functions in setup.ts also used
the vulnerable NODE_ENV !== 'production' check. Extract isDevEnvironment()
as a shared helper used by both HTTP and WebSocket auth paths.

Also fix test names (empty vs undefined) and add isDevEnvironment unit tests.
…Server (VoltAgent#1204)

Tests were mocking the old app.listen() API, but startServer() was refactored
to use Node.js http.createServer() + server.listen(). Updated mocks to target
node:http module instead, restoring test coverage for all 6 test cases.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 17, 2026

🦋 Changeset detected

Latest commit: bcb54c8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@voltagent/server-core Patch
@voltagent/server-elysia Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Introduces isDevEnvironment() (returns true only for NODE_ENV === "development" or "test") and gates dev auth bypass and WebSocket dev checks through it (fail-closed for empty/undefined NODE_ENV). Also updates Elysia server provider tests to mock node:http.createServer instead of app.listen.

Changes

Cohort / File(s) Summary
Changesets
\.changeset/fix-dev-auth-bypass-node-env.md, \.changeset/fix-elysia-server-provider-tests.md
Add patch release notes documenting the dev auth gating change and test mock alignment.
Auth Environment Gating
packages/server-core/src/auth/utils.ts, packages/server-core/src/auth/utils.spec.ts
Add export function isDevEnvironment(); update isDevRequest() to use it; expand tests to assert fail-closed behavior for empty/undefined NODE_ENV and explicit "development"/"test" cases.
WebSocket Setup
packages/server-core/src/websocket/setup.ts
Replace direct NODE_ENV !== "production" checks with isDevEnvironment() for header- and query-param-based dev bypass detection.
Elysia Provider Tests
packages/server-elysia/src/elysia-server-provider.spec.ts
Mock node:http.createServer (listen, close, once) and update tests to assert createServer/mockServer.listen usage and to simulate listen-time errors via captured once("error", ...) handler.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • omeraplak

Poem

🐰
I hopped in code where NODE_ENV hid,
Closed the gate unless dev or test were bid,
Headers no longer grant a secret pass,
WebSockets follow the same steadfast class,
Tests now mock Node's server — tidy and sped.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Beyond fixing the core issue #1204 (Elysia server tests), the PR includes changes to @voltagent/server-core authentication logic (isDevEnvironment function, NODE_ENV validation, WebSocket dev bypass updates) and corresponding test coverage, which are not mentioned in issue #1204's objectives. Clarify whether the @voltagent/server-core auth changes should be in a separate PR focused on dev auth bypass security, or confirm they are intentionally bundled with the Elysia test fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: updating Elysia server provider test mocks to align with the refactored http.createServer implementation, and references the fixed issue.
Description check ✅ Passed The description comprehensively addresses the PR checklist requirements: it links to issue #1204, documents all changes made, provides test results showing all six tests pass, includes changesets for both affected packages, and includes reviewer notes.
Linked Issues check ✅ Passed The code changes fully meet the requirements from issue #1204: mocking node:http.createServer, updating test setup and assertions to verify createServer/server.listen/server.close calls, and simulating startup errors via server.once('error'). All six failing tests now pass.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
packages/server-elysia/src/elysia-server-provider.spec.ts (2)

22-32: Module-level mockServer is shared across tests.

Since mockServer is defined at module scope and vi.clearAllMocks() only resets call history (not implementations or properties), state like listening: true persists across tests. The current beforeEach re-applies implementations for listen/close/once, which is sufficient for the present test set, but any future test that mutates mockServer.listening (or adds new methods) will leak into other tests. Consider resetting mockServer properties explicitly in beforeEach to make the isolation boundary obvious.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-elysia/src/elysia-server-provider.spec.ts` around lines 22 -
32, The module-level mockServer object is shared across tests causing state
leakage; in the beforeEach where you reapply implementations for
listen/close/once, also explicitly reset mockServer properties (e.g., set
mockServer.listening = false or true as appropriate and reinitialize any added
methods) so each test starts with a fresh known state; update the beforeEach to
reassign mockServer.listening and any other mutable fields on the mockServer
used by tests (while keeping vi.mock("node:http") and the mocked
listen/close/once implementations intact).

139-160: Startup error simulation relies on registration order — worth a brief comment.

The test works because the implementation calls server.once("error", reject) before server.listen(...) (see elysia-server-provider.ts:87-91), so errorHandler is captured before mockServer.listen synchronously invokes it. If the implementation ever reorders these calls, errorHandler would be undefined and the test would silently hang on an unresolved promise rather than fail loudly. A short inline comment noting this ordering dependency, or a defensive expect(errorHandler).toBeDefined() before triggering it, would make the failure mode more diagnosable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-elysia/src/elysia-server-provider.spec.ts` around lines 139 -
160, The test relies on server.once("error", ...) being called before
server.listen(...) so errorHandler is set; add a defensive check (or short
inline comment) in the test to make this dependency explicit and fail loudly if
ordering changes: after mockServer.once.mockImplementation and before invoking
mockServer.listen (or before calling provider.start()), assert that errorHandler
is defined (e.g., expect(errorHandler).toBeDefined()) and/or add a brief comment
referencing the ordering dependency; this targets the errorHandler captured in
the it("should handle startup errors and release port") block and ensures the
test won't hang silently if server.once is registered after listen.
packages/server-core/src/auth/utils.spec.ts (1)

9-29: Consider adding an explicit undefined NODE_ENV case.

The suite covers "development", "test", "production", and "", but the docstring on isDevEnvironment() emphasizes that undefined NODE_ENV is the primary fail-closed scenario (deployments that forgot to set it). vi.stubEnv("NODE_ENV", undefined) (or deleting via delete process.env.NODE_ENV inside the test) would pin down that behavior explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-core/src/auth/utils.spec.ts` around lines 9 - 29, Add an
explicit test to assert that isDevEnvironment() returns false when NODE_ENV is
undefined: inside the existing "isDevEnvironment" describe block add a case that
clears or unsets the env (e.g., use vi.stubEnv("NODE_ENV", undefined) or delete
process.env.NODE_ENV) and expect(isDevEnvironment()).toBe(false); this pins down
the fail-closed behavior for undefined NODE_ENV and uses the same test helpers
(vi.stubEnv) already present in the suite.
.changeset/fix-dev-auth-bypass-node-env.md (1)

5-12: Consider mentioning test in the headline for completeness.

The title says "require explicit NODE_ENV=development" but the body (and implementation) also accepts NODE_ENV=test. A small wording tweak would prevent confusion for consumers scanning the changelog.

📝 Proposed wording
-fix(auth): require explicit NODE_ENV=development for dev auth bypass
+fix(auth): require explicit NODE_ENV=development|test for dev auth bypass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/fix-dev-auth-bypass-node-env.md around lines 5 - 12, Update the
changelog headline to mention both development and test environments so it
matches the implementation: change the title in
.changeset/fix-dev-auth-bypass-node-env.md from "require explicit
NODE_ENV=development for dev auth bypass" to something like "require explicit
NODE_ENV=development or NODE_ENV=test for dev auth bypass" and ensure any
summary lines referencing isDevRequest() or the dev bypass behavior also mention
"test" to avoid consumer confusion.
packages/server-core/src/auth/utils.ts (1)

45-58: Consider aligning shouldEnableSwaggerUI with the new helper.

packages/server-core/src/server/app-setup.ts still uses a direct process.env.NODE_ENV === "production" check with fail-open semantics (any non-production value enables Swagger UI). It's not strictly a security issue since Swagger exposure is less sensitive than auth bypass, but for consistency — and to avoid the same "undefined NODE_ENV on deployed server" footgun — consider switching it to !isDevEnvironment()-style gating, or at minimum a shared constant, so env checks across the package stay uniform.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-core/src/auth/utils.ts` around lines 45 - 58, The server
app-setup uses a direct process.env.NODE_ENV === "production" check for
shouldEnableSwaggerUI which is inconsistent with the new
isDevEnvironment()/isDevRequest() helper pattern; update shouldEnableSwaggerUI
in packages/server-core/src/server/app-setup.ts to use the shared
isDevEnvironment() (or the inverse !isDevEnvironment()) instead of comparing
NODE_ENV directly so that Swagger gating follows the same environment logic as
isDevRequest(); ensure you import isDevEnvironment from auth/utils and replace
the existing production-check branch with a call to isDevEnvironment() (or a
shared constant) to maintain uniform behavior across the package.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.changeset/fix-dev-auth-bypass-node-env.md:
- Around line 5-12: Update the changelog headline to mention both development
and test environments so it matches the implementation: change the title in
.changeset/fix-dev-auth-bypass-node-env.md from "require explicit
NODE_ENV=development for dev auth bypass" to something like "require explicit
NODE_ENV=development or NODE_ENV=test for dev auth bypass" and ensure any
summary lines referencing isDevRequest() or the dev bypass behavior also mention
"test" to avoid consumer confusion.

In `@packages/server-core/src/auth/utils.spec.ts`:
- Around line 9-29: Add an explicit test to assert that isDevEnvironment()
returns false when NODE_ENV is undefined: inside the existing "isDevEnvironment"
describe block add a case that clears or unsets the env (e.g., use
vi.stubEnv("NODE_ENV", undefined) or delete process.env.NODE_ENV) and
expect(isDevEnvironment()).toBe(false); this pins down the fail-closed behavior
for undefined NODE_ENV and uses the same test helpers (vi.stubEnv) already
present in the suite.

In `@packages/server-core/src/auth/utils.ts`:
- Around line 45-58: The server app-setup uses a direct process.env.NODE_ENV ===
"production" check for shouldEnableSwaggerUI which is inconsistent with the new
isDevEnvironment()/isDevRequest() helper pattern; update shouldEnableSwaggerUI
in packages/server-core/src/server/app-setup.ts to use the shared
isDevEnvironment() (or the inverse !isDevEnvironment()) instead of comparing
NODE_ENV directly so that Swagger gating follows the same environment logic as
isDevRequest(); ensure you import isDevEnvironment from auth/utils and replace
the existing production-check branch with a call to isDevEnvironment() (or a
shared constant) to maintain uniform behavior across the package.

In `@packages/server-elysia/src/elysia-server-provider.spec.ts`:
- Around line 22-32: The module-level mockServer object is shared across tests
causing state leakage; in the beforeEach where you reapply implementations for
listen/close/once, also explicitly reset mockServer properties (e.g., set
mockServer.listening = false or true as appropriate and reinitialize any added
methods) so each test starts with a fresh known state; update the beforeEach to
reassign mockServer.listening and any other mutable fields on the mockServer
used by tests (while keeping vi.mock("node:http") and the mocked
listen/close/once implementations intact).
- Around line 139-160: The test relies on server.once("error", ...) being called
before server.listen(...) so errorHandler is set; add a defensive check (or
short inline comment) in the test to make this dependency explicit and fail
loudly if ordering changes: after mockServer.once.mockImplementation and before
invoking mockServer.listen (or before calling provider.start()), assert that
errorHandler is defined (e.g., expect(errorHandler).toBeDefined()) and/or add a
brief comment referencing the ordering dependency; this targets the errorHandler
captured in the it("should handle startup errors and release port") block and
ensures the test won't hang silently if server.once is registered after listen.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fcbab152-366c-47a8-97b9-9b525ba07170

📥 Commits

Reviewing files that changed from the base of the PR and between 71c9f84 and e639394.

📒 Files selected for processing (6)
  • .changeset/fix-dev-auth-bypass-node-env.md
  • .changeset/fix-elysia-server-provider-tests.md
  • packages/server-core/src/auth/utils.spec.ts
  • packages/server-core/src/auth/utils.ts
  • packages/server-core/src/websocket/setup.ts
  • packages/server-elysia/src/elysia-server-provider.spec.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server-elysia/src/elysia-server-provider.spec.ts (1)

139-160: ⚠️ Potential issue | 🟡 Minor

Startup-error test relies on call ordering in the implementation — add a guard assertion.

This test works only because startServer() calls server.once("error", reject) before server.listen(...), so errorHandler is captured prior to the synchronous listen mock firing it. If the implementation is ever refactored to register the error listener after listen (or via on instead of once), errorHandler will be undefined and the if (errorHandler) branch will silently no-op, causing provider.start() to hang instead of rejecting — the test would then time out rather than fail cleanly.

Consider asserting the handler was captured and/or the event name, so a regression surfaces as an explicit failure:

🛡️ Suggested hardening
     mockServer.listen.mockImplementation(() => {
       // Simulate an error during listen by calling the error handler
-      if (errorHandler) {
-        errorHandler(new Error("Startup failed"));
-      }
+      if (!errorHandler) {
+        throw new Error("Test setup: error handler was not registered before listen()");
+      }
+      errorHandler(new Error("Startup failed"));
       return mockServer;
     });

     await expect(provider.start()).rejects.toThrow("Startup failed");
     expect(portManager.releasePort).toHaveBeenCalledWith(3000);
+    expect(mockServer.once).toHaveBeenCalledWith("error", expect.any(Function));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-elysia/src/elysia-server-provider.spec.ts` around lines 139 -
160, The test relies on capturing errorHandler before mockServer.listen fires;
make this explicit by asserting the handler was registered before forcing the
listen error: after mockServer.once registration logic (or before calling
provider.start()), add an assertion like expect(errorHandler).toBeDefined() or
verify mockServer.once was called with "error" so the test fails loudly if the
implementation registers listeners after listen; alternatively, change the
mockServer.listen implementation to throw or fail if errorHandler is undefined
to ensure provider.start() doesn't hang. Ensure you reference the existing
errorHandler variable, mockServer.once and mockServer.listen mocks, and the
provider.start() call when adding the guard.
🧹 Nitpick comments (1)
packages/server-elysia/src/elysia-server-provider.spec.ts (1)

22-32: Optional: reset mockServer.listening per test and/or localize the mock.

mockServer is module-scoped with listening: true hardcoded. vi.clearAllMocks() in afterEach clears call history but does not reset property values or re-apply implementations — you already re-seed listen/close/once in beforeEach, but listening is never reset. Today no test mutates it, so it's harmless, but if a future test toggles mockServer.listening = false (e.g., to cover the "server not listening, skip close" branch in stopServer), state will leak across tests in this file. Consider resetting it in beforeEach:

♻️ Suggested tweak
   beforeEach(() => {
     vi.spyOn(appFactory, "createApp").mockResolvedValue({ app: mockApp } as any);
     provider = new ElysiaServerProvider(mockDeps, { port: 3000 });

     // Reset mock server behavior for each test
+    mockServer.listening = true;
     mockServer.listen.mockImplementation((_port, _hostname, callback) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server-elysia/src/elysia-server-provider.spec.ts` around lines 22 -
32, The module-scoped mockServer has listening: true that isn't reset between
tests; update the test setup so mockServer.listening is re-initialized each test
(either set mockServer.listening = true in beforeEach or create the mock object
inside beforeEach and have vi.mock("node:http", ...) return a fresh instance) so
state doesn't leak across tests—ensure the mocked createServer implementation
used by the tests (the vi.mock factory and the mockServer referenced by
beforeEach/afterEach and any tests calling stopServer) always gets a fresh
listening value per test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/server-elysia/src/elysia-server-provider.spec.ts`:
- Around line 139-160: The test relies on capturing errorHandler before
mockServer.listen fires; make this explicit by asserting the handler was
registered before forcing the listen error: after mockServer.once registration
logic (or before calling provider.start()), add an assertion like
expect(errorHandler).toBeDefined() or verify mockServer.once was called with
"error" so the test fails loudly if the implementation registers listeners after
listen; alternatively, change the mockServer.listen implementation to throw or
fail if errorHandler is undefined to ensure provider.start() doesn't hang.
Ensure you reference the existing errorHandler variable, mockServer.once and
mockServer.listen mocks, and the provider.start() call when adding the guard.

---

Nitpick comments:
In `@packages/server-elysia/src/elysia-server-provider.spec.ts`:
- Around line 22-32: The module-scoped mockServer has listening: true that isn't
reset between tests; update the test setup so mockServer.listening is
re-initialized each test (either set mockServer.listening = true in beforeEach
or create the mock object inside beforeEach and have vi.mock("node:http", ...)
return a fresh instance) so state doesn't leak across tests—ensure the mocked
createServer implementation used by the tests (the vi.mock factory and the
mockServer referenced by beforeEach/afterEach and any tests calling stopServer)
always gets a fresh listening value per test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5bdc7a9d-38fa-424f-954d-9c87740aa83d

📥 Commits

Reviewing files that changed from the base of the PR and between e639394 and bcb54c8.

📒 Files selected for processing (2)
  • packages/server-core/src/auth/utils.ts
  • packages/server-elysia/src/elysia-server-provider.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server-core/src/auth/utils.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Elysia server provider test suite entirely broken — stale mocks after Node.js HTTP refactor

1 participant